内核开发的特点: + 不能访问C库,不能访问标准的C头文件 + 必须使用GNU C + 缺乏内存保护机制 + 难以执行浮点运算 + 每个进程只有很小的定长堆栈 + 必须注意同步和并发 + 要考虑可移植性

对内核来说,完整的C库(或者C库的子集)太大且太低效了,但是大部分常用的C库函数在内核中都已经得到实现:如<linux/string.h>

内核使用的基本的头文件在内核代码树的include目录,如include/linux/string.h 体系结构相关的头文件在内核代码树的arch/<architecture>/include/asm目录下,内核通过asm/为前缀的方式包含这些头文件,如<asm/ioctl.h>

内核的打印函数是printk,printk函数负责把格式化好的字符串拷贝到内核日志缓冲区上,这样syslogd程序就可以通过读取该缓冲区来获取内核信息。 printk和printf很像,但有一个显著区别:printk可以通过指定一个标志来设置优先级,syslogd会根据这个优先级标志来决定在什么地方显示这条系统信息。

printk(KERN_ERR "this is an error!\n"); KERN_ERR和打印内容之间没有逗号,因为优先级标志是预处理程序定义的一个描述性字符串,在编译时优先级标志就与要打印的消息绑定在一起处理。

内核开发使用的C语言涵盖了ISO C99 标准和GNU C拓展特性。

gcc是多种GNU编译器的集合,它包含的C编译器既可以编译内核,也可以编译Linux系统上的C语言程序

内核使用到的GNU C拓展特性: + 内联(inline)函数:内联函数必须在使用之前就定义好,否则编译器无法展开,一般在头文件中定义内联函数(如果只在某个文件里使用,可以定义在该文件开始的地方)。定义一个内联函数的时候需要同时使用static关键字和inline关键字,这样编译时才不会为内联函数单独建立一个函数体。

static inline void wolf(unsigned long tail_size)
  • 内联汇编:gcc编译器支持在C函数中嵌入汇编指令(需要知道对应的体系结构才能使用这个功能)。使用asm()指令嵌入汇编代码
//执行x86处理器的rdtsc指令,返回时间戳(tsc)寄存器的值
unsigned int low, high;
asm volatile("rdtsc" : "=a" (low), "=d" (high));
// low和high分别包含64位时间戳的低32位和高32位
  • 分支声明:对于条件选择语句,gcc内建了一条指令用于优化,在一个条件经常出现,或者该条件很少出现的时候,编译器可以根据这条指令对分支选择进行优化。
// 把分支标记为绝少发生的选择
if(unlikely(error)) {
	/*...*/
}

// 把分支标记为通常为真的选择
if(likely(success)) {
	/*...*/
}

用户程序如果访问非法内存,内核可以发现这个错误并发送SIGSEGV信号;内核程序访问非法内存,没法发现错误,一般会直接导致oops。 内存没有分页,每用掉一个字节,物理内存就减少一个字节。

在用户空间进行浮点数运算,内核可以陷入并完成从整数操作到浮点数操作的模式转换。内核空间不能完美的支持浮点操作,因为它本身不能陷入,在内核中使用浮点数时,除了要人工保存和恢复浮点寄存器,还有其它琐碎事情要做。 除了一些极少的情况,不要在内核使用浮点操作。

用户空间的程序可以从栈上分配大量的空间来存放变量,是因为用户空间的栈本身比较大,且能动态增长。 内核栈的准确大小随体系结构而变。在x86上,栈的大小在编译时配置,可以是4kb也可以是8kb。从历史上说,内核栈的大小是两页,即32位机的内核栈是8kb,64位机是16kb。 每个处理器都有自己的栈。

内核很容易产生竞争条件,内核的许多特性要求能够并发地共享数据,这就要求有同步机制以保证不出现竞争条件: + Linux是抢占多任务操作系统,进程随时会被进行调度 + Linux内核支持对称多处理器系统(SMP),两个处理器可能同时访问共享的同一个资源 + 中断是异步到来的,如果不适当保护访问的资源,中断程序就可能访问同一资源 + Linux内核可以抢占,如果不加以适当保护,抢占的进程可能和被抢占的进程访问相同的资源

Linux是一个可移植的操作系统,内核代码的大部分C代码应该是与体系结构无关的,必须把与体系结构相关的代码从内核代码树的特定目录中适当地分离出来。 可移植性的一些准则: + 保持字节序 + 64位对齐 + 不假定字长和页面长度